/* File descriptors */

#include "types.h"
#include "fs.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "file.h"
#include "console.h"
#include "log.h"

struct devsw devsw[NDEV];
struct {
    struct spinlock lock;
    struct file file[NFILE];
} ftable;

/* Optional since BSS is zero-initialized. */
void
fileinit()
{
    initlock(&ftable.lock, "ftable.lock");
}

/* Allocate a file structure. */
struct file *
filealloc()
{
    acquire(&ftable.lock);
    for (int i = 0; i < NFILE; i++) {
        struct file *f = ftable.file + i;
        if (f->ref == 0) {
            f->ref = 1;
            release(&ftable.lock);
            return f;
        }
    }
    release(&ftable.lock);

    return 0;
}

/* Increment ref count for file f. */
struct file *
filedup(struct file *f)
{
    acquire(&ftable.lock);
    assert(f->ref > 0);
    f->ref++;
    release(&ftable.lock);
    return f;
}

/* Close file f. (Decrement ref count, close when reaches 0.) */
void
fileclose(struct file *f)
{
    struct inode *ip = 0;

    acquire(&ftable.lock);

    assert(f->ref > 0);
    f->ref--;

    if (f->ref == 0) {
        if (f->type == FD_INODE)
            ip = f->ip;
        f->type = FD_NONE;
    }

    release(&ftable.lock);

    if (ip) {
        begin_op();
        iput(ip);
        end_op();
    }
}

/* Get metadata about file f. */
int
filestat(struct file *f, struct stat *st)
{
    if (f->type == FD_NONE)
        return -1;

    ilock(f->ip);
    stati(f->ip, st);
    iunlock(f->ip);
    return 0;
}

/* Read from file f. */
ssize_t
fileread(struct file *f, char *addr, ssize_t n)
{
    assert(f->type != FD_NONE);
    if (!f->readable)
        return -1;

    ilock(f->ip);

    ssize_t inc = readi(f->ip, addr, f->off, n);
    if (inc > 0)
        f->off += inc;

    iunlock(f->ip);

    return inc;
}

/* Write to file f. */
ssize_t
filewrite(struct file *f, char *addr, ssize_t n)
{
    assert(f->type != FD_NONE);
    if (!f->writable)
        return -1;

    /*
    ilock(f->ip);
    begin_op();
    ssize_t inc = writei(f->ip, addr, f->off, n);
    end_op();
    if (inc > 0)
        f->off += inc;
    iunlock(f->ip);
    */

    // we have to split write into multiple transactions

    ssize_t MAXSIZE = 1024;  // 1KB
    assert(MAXSIZE / BSIZE + 4 < MAXOPBLOCKS);

    ssize_t i = 0;
    while (i < n) {
        ssize_t nw = MIN(MAXSIZE, n - i);

        begin_op();
        ilock(f->ip);
        ssize_t inc = writei(f->ip, addr + i, f->off, nw);
        if (inc > 0)
            f->off += inc;
        iunlock(f->ip);
        end_op();

        if (inc != nw)
            break;
        i += inc;
    }

    return i < n ? -1 : n;
}
